////
/// @group core.theme
////

@use "sass:color";
@use "sass:list";
@use "sass:map";
@use "sass:meta";
@use "sass:string";
@use "colors";
@use "a11y";

@use "../utils";

/// This defines the total color scheme for your app and can be either:
/// - `light`
/// - `dark`
/// - `system`
///
/// Setting this value to `system` will default to a light theme unless the user
/// has set their preferred color scheme to dark on their OS.
///
/// @type String
$color-scheme: light !default;

/// The dark elevation feature is generally recommended to keep enabled since it
/// helps with visibility when your app is using the dark color scheme either
/// through `$color-scheme: dark` or `using the `use-dark-theme mixin. When this
/// feature is enabled, the `background-color` for surfaces will gradually
/// become lighter the higher their `z-value` is with the material design box
/// shadows.
///
/// If this is set to `false`, all surfaces will mimic the light theme and have
/// a single surface color.
///
/// @type Boolean
$disable-dark-elevation: $color-scheme == light !default;

/// Set this to `true` if you do not want the initial bundle to include the
/// `@media (prefers-color-scheme: dark)` styles.
///
/// @type Boolean
$disable-default-system-theme: false !default;

/// Set this to `true` if you do not want the initial bundle to include the
/// `:root { ... variables ... }` styles.
///
/// @type Boolean
$disable-default-root-theme: false !default;

/// This should only be used if your application does not use menu, dialog,
/// sheet, card, expansion-panel, select, app-bar (theme="surface")
///
/// @type Boolean
$disable-surface-color: false !default;

/// Set to `true` if you don't want to create a primary color variable.
/// @type Boolean
$disable-primary-color: false !default;

/// Set to `true` if you don't want to create a on-primary color variable.
/// @type Boolean
$disable-on-primary-color: $disable-primary-color !default;

/// Set to `true` if you don't want to create a secondary color variable.
/// @type Boolean
$disable-secondary-color: false !default;

/// Set to `true` if you don't want to create a on-secondary color variable.
/// @type Boolean
$disable-on-secondary-color: $disable-secondary-color !default;

/// Set to `true` if you don't want to create a warning color variable.
/// @type Boolean
$disable-warning-color: false !default;

/// Set to `true` if you don't want to create a on-warning color variable.
/// @type Boolean
$disable-on-warning-color: $disable-warning-color !default;

/// Set to `true` if you don't want to create a error color variable.
/// @type Boolean
$disable-error-color: false !default;

/// Set to `true` if you don't want to create a on-error color variable.
/// @type Boolean
$disable-on-error-color: $disable-error-color !default;

/// Set to `true` if you don't want to create a success color variable.
/// @type Boolean
$disable-success-color: false !default;

/// Set to `true` if you don't want to create a on-success color variable.
/// @type Boolean
$disable-on-success-color: $disable-success-color !default;

/// Set to `true` if you don't want to create a outline grey color variable.
/// @type Boolean
$disable-outline-grey-color: false !default;

/// Set to `true` if you don't want to create a text-primary color variable.
/// @type Boolean
$disable-text-primary-color: false !default;

/// Set to `true` if you don't want to create a text-secondary color variable.
/// @type Boolean
$disable-text-secondary-color: false !default;

/// Set to `true` if you don't want to create a text-hint color variable.
/// @type Boolean
$disable-text-hint-color: false !default;

/// Set to `true` if you don't want to create a text-disabled color variable.
/// @type Boolean
$disable-text-disabled-color: false !default;

/// The primary theme color to use throughout the app.
/// @type Color
$primary-color: colors.$blue-500 !default;

/// An accessible text color to use when the primary color is a background.
/// @type Color
$on-primary-color: a11y.contrast-color($primary-color) !default;

/// The secondary theme color to use throughout the app.
/// @type Color
$secondary-color: colors.$orange-a-400 !default;

/// An accessible text color to use when the secondary color is a background.
/// @type Color
$on-secondary-color: a11y.contrast-color($secondary-color) !default;

/// The warning color to use throughout the app.
/// @type Color
$warning-color: colors.$deep-orange-a-400 !default;

/// An accessible text color to use when the warning color is a background.
/// @type Color
$on-warning-color: a11y.contrast-color($warning-color) !default;

/// The error color to use throughout the app.
/// @type Color
$error-color: colors.$red-500 !default;

/// An accessible text color to use when the error color is a background.
/// @type Color
$on-error-color: a11y.contrast-color($error-color) !default;

/// The success color to use throughout the app.
/// @type Color
$success-color: colors.$green-a-700 !default;

/// An accessible text color to use when the success color is a background.
/// @type Color
$on-success-color: a11y.contrast-color($success-color) !default;

/// The default outline-width to apply for focus, borders, etc
/// @type Number
$outline-width: 0.0625em !default;

/// The color to use for the outline.
/// @type Color
$outline-color: currentcolor !default;

/// The color to use when the outline should be grey.
/// @type Color
$outline-grey-color: #999 !default;

/// The base inverse color to use when using the light
/// theme. This should probably never be changed.
/// @type Color
$light-theme-inverse-color: colors.$black !default;

/// The root background color (html) when using the light theme.
/// @type Color
$light-theme-background-color: colors.$grey-50 !default;

/// The background color to apply for surfaces/temporary elements when using the
/// light theme. So things like dialogs and menus.
/// @type Color
$light-theme-surface-color: colors.$white !default;

/// The primary text color when using the light theme.
/// @type Color
$light-theme-text-primary-color: rgba(
  $light-theme-inverse-color,
  0.87
) !default;

/// The secondary text color when using the light theme.
/// @type Color
$light-theme-text-secondary-color: rgba(
  $light-theme-inverse-color,
  0.6
) !default;

/// The hint text color when using the light theme.
/// @type Color
$light-theme-text-hint-color: rgba($light-theme-inverse-color, 0.38) !default;

/// The disabled text color when using the light theme.
/// @type Color
$light-theme-text-disabled-color: rgba(
  $light-theme-inverse-color,
  0.38
) !default;

/// This should probably never be changed.
/// @type Color
$dark-theme-inverse-color: colors.$white !default;

/// The background color to apply for surfaces/temporary elements when using the
/// dark theme. So things like dialogs and menus.
/// @type Color
$dark-theme-background-color: #121212 !default;

/// The default surface background color to apply for surfaces/temporary elements
/// when using the dark theme.
///
/// This value is a bit different than the light theme variant since dark mode
/// supports an "elevation mode" where each surface gains a different background
/// color depending on its elevation.
///
/// @type Color
$dark-theme-surface-color: if(
  $disable-dark-elevation,
  colors.$grey-800,
  null
) !default;

/// The primary text color when using the dark theme.
/// @type Color
$dark-theme-text-primary-color: rgba($dark-theme-inverse-color, 0.87) !default;

/// The secondary text color when using the dark theme.
/// @type Color
$dark-theme-text-secondary-color: rgba($dark-theme-inverse-color, 0.6) !default;

/// The hint text color when using the dark theme.
/// @type Color
$dark-theme-text-hint-color: rgba($dark-theme-inverse-color, 0.38) !default;

/// The disabled text color when using the dark theme.
/// @type Color
$dark-theme-text-disabled-color: rgba($dark-theme-inverse-color, 0.38) !default;

/// Used to provide the correct color based on the `$color-scheme` setting.
///
/// @param {Color} light-color - The light theme color
/// @param {Color} dark-color - The dark theme color
/// @returns {Color} The correct color depending on the `$color-scheme`
@function get-default-color($light-color, $dark-color) {
  @return if($color-scheme == dark, $dark-color, $light-color);
}

/// The default background color
/// @type Color
$background-color: get-default-color(
  $light-theme-background-color,
  $dark-theme-background-color
) !default;

/// The default surface color
/// @type Color
$surface-color: get-default-color(
  $light-theme-surface-color,
  $dark-theme-surface-color
) !default;

/// The default text primary color
/// @type Color
$text-primary-color: get-default-color(
  $light-theme-text-primary-color,
  $dark-theme-text-primary-color
) !default;

/// The default text secondary color
/// @type Color
$text-secondary-color: get-default-color(
  $light-theme-text-secondary-color,
  $dark-theme-text-secondary-color
) !default;

/// The default text hint color
/// @type Color
$text-hint-color: get-default-color(
  $light-theme-text-hint-color,
  $dark-theme-text-hint-color
) !default;

/// The default text disabled color
/// @type Color
$text-disabled-color: get-default-color(
  $light-theme-text-disabled-color,
  $dark-theme-text-disabled-color
) !default;

/// This should probably never be changed.
/// @type Color
$inverse-color: get-default-color(
  $light-theme-inverse-color,
  $dark-theme-inverse-color
) !default;

/// This is a Map for all the supported material design elevations (0 - 24) and
/// the background color to use for that elevation. Check out
/// `$dark-elevation-colors` and `$custom-dark-elevation-colors` for
/// how to change these values.;
///
/// @type Map
$default-dark-elevation-colors: (
  0: $dark-theme-background-color,
  1: #1f1f1f,
  2: #242424,
  3: #262626,
  4: #282828,
  5: #282828,
  6: #2c2c2c,
  7: #2c2c2c,
  8: #2f2f2f,
  9: #2f2f2f,
  10: #2f2f2f,
  11: #2f2f2f,
  12: #333,
  13: #333,
  14: #333,
  15: #333,
  16: #353535,
  17: #353535,
  18: #353535,
  19: #353535,
  20: #353535,
  21: #353535,
  22: #353535,
  23: #353535,
  24: #383838,
);

/// This Map can be used to override the `$default-dark-elevation-colors`
/// with new values if you only want to change a few values.
///
/// @example scss - Adding Custom Elevation Colors<!-- no-compile -->
///   @use "@react-md/core" with (
///     $custom-dark-elevation-colors: (
///       1: #000,
///       2: #1f1f1f,
///     )
///   );
///
/// @type Map
$custom-dark-elevation-colors: () !default;

/// This Map can be used to completely configure the dark elevation colors and
/// remove unused elevations.
///
/// Elevations:
///
/// - 0 - Root background color
/// - 1 - Raisable card starting elevation, Switch ball
/// - 2 - Sheet default elevation
/// - 4 - Sticky table header/footer inactive styles
/// - 8 - Raisable card ending elevation, Raisable chip, Menu
/// - 12 - Solid chip background color
/// - 16 - Dialogs, Sheet raised elevation
///
/// @example scss - Used Colors Only<!-- no-compile -->
///   @use "@react-md/core" with (
///     $dark-elevation-colors: (
///       0: #121212,
///       1: #1f1f1f,
///       2: #242424,
///       4: #282828,
///       8: #2f2f2f,
///       12: #333,
///       16: #353535,
///     )
///   );
///
/// @type Map
$dark-elevation-colors: map.merge(
  $default-dark-elevation-colors,
  $custom-dark-elevation-colors
) !default;

/// Used to just ensure that the user overrides the `$color-scheme` with a
/// supported value
/// @access private
/// @type String
$_validated_color_scheme: utils.validate(
  (light dark system),
  $color-scheme,
  "$color-scheme"
);

/// The available configurable css variables and mostly used internally for the
/// `get-var`, `set-var`, and `use-var` utils.
/// @type List
$theme-variables: (
  background-color,
  on-background-color,
  surface-color,
  primary-color,
  on-primary-color,
  secondary-color,
  on-secondary-color,
  warning-color,
  on-warning-color,
  success-color,
  on-success-color,
  error-color,
  on-error-color,
  text-primary-color,
  text-secondary-color,
  text-hint-color,
  text-disabled-color,
  outline-width,
  outline-color,
  outline-grey-color,
  inverse-color
);

/// Checks if a theme variable is disabled and should not be able to be
/// referenced.
///
/// @access private
/// @param {String} name - The variable name
/// @returns {Boolean} the feature flag value
@function _is-var-disabled($name) {
  @if $name == surface-color {
    @return $disable-surface-color;
  }

  @if $name == primary-color {
    @return $disable-primary-color;
  }

  @if $name == on-primary-color {
    @return $disable-on-primary-color;
  }

  @if $name == secondary-color {
    @return $disable-secondary-color;
  }

  @if $name == on-secondary-color {
    @return $disable-on-secondary-color;
  }

  @if $name == warning-color {
    @return $disable-warning-color;
  }

  @if $name == on-warning-color {
    @return $disable-on-warning-color;
  }

  @if $name == error-color {
    @return $disable-error-color;
  }

  @if $name == on-error-color {
    @return $disable-on-error-color;
  }

  @if $name == success-color {
    @return $disable-success-color;
  }

  @if $name == on-success-color {
    @return $disable-on-success-color;
  }

  @if $name == text-primary-color {
    @return $disable-text-primary-color;
  }

  @if $name == text-secondary-color {
    @return $disable-text-secondary-color;
  }

  @if $name == text-hint-color {
    @return $disable-text-hint-color;
  }

  @if $name == text-disabled-color {
    @return $disable-text-disabled-color;
  }

  @if $name == outline-grey-color {
    @return $disable-outline-grey-color;
  }

  @return false;
}

/// This is mostly used internally to prevent applying default theme variables
/// to other elements so they don't need to be overwritten as well when changing
/// the theme for a part of the app.
///
/// So this is used when a variable is set in the generic `@mixin variables`
/// call and then later used in a `get-var/set-var` call with the
/// `theme-color-var-fallback` function.
///
/// @param {any} value - The value to check against
/// @return {Boolean} true if the provided value is one of the
/// `theme-get-var()` results without any fallback provided
@function is-theme-color-var($value) {
  @each $name in $theme-variables {
    @if theme-get-var($name) == $value {
      @return true;
    }
  }

  @return false;
}

/// See the `is-theme-color-var` for info.
///
/// @param {any} value - The value to maybe return
/// @return {any} the `$value` if it was theme var, otherwise null
@function theme-color-var-fallback($value) {
  @return if(is-theme-color-var($value), $value, null);
}

/// @param {Number} z-value - The dark elevation value
/// @returns {String} the dark elevation color var name
@function _dark-elevation-color($z-value) {
  @return "--rmd-dark-elevation-color-#{$z-value}";
}

/// @param {Number} z-value - The dark elevation value
/// @returns {String} the dark elevation color var
@function get-dark-elevation-color($z-value) {
  @return var(#{_dark-elevation-color($z-value)});
}

/// @param {Number} z-value - The dark elevation value
/// @param {String} value - The value to set
@mixin set-dark-elevation-color($z-value, $value) {
  @if $value {
    #{_dark-elevation-color($z-value)}: #{$value};
  }
}

/// @param {String} name - The supported variable name
/// @param {any} fallback [null] - An optional fallback value
/// @returns {String} a `var()` statement
@function theme-get-var($name, $fallback: null) {
  @if _is-var-disabled($name) {
    @return $fallback;
  }

  $var: utils.get-var-name($theme-variables, $name, "theme");

  @if $fallback {
    @return var(#{$var}, #{$fallback});
  }

  @return var(#{$var});
}

/// @param {String} name - The supported variable name
/// @param {any} value-or-theme-name - The value to set the variable to.
/// Supports `null` which will just be a no-op.
@mixin theme-set-var($name, $value-or-theme-name) {
  @if _is-var-disabled($name) {
    @error '"#{$name}" is currently disabled and cannot be changed. Set "$disable-#{$name}" to `false` or remove it from the Sass module overrides.';
  }

  $var: utils.get-var-name($theme-variables, $name, "theme");
  $value: $value-or-theme-name;
  @if list.index($theme-variables, $value-or-theme-name) {
    $name: $value-or-theme-name;
    $value: theme-get-var($name);
  }

  #{$var}: #{$value};
}

/// @param {String} property - The css property to apply the variable to
/// @param {String} name - The supported variable name
/// @param {any} fallback [null] - An optional fallback value if the variable
/// has not been set
@mixin theme-use-var($property, $name: $property, $fallback: null) {
  @if _is-var-disabled($name) {
    @error '"#{$name}" is currently disabled and cannot be changed. Set "$disable-#{$name}-var" to `true` or remove it from the Sass module overrides.';
  }

  #{$property}: theme-get-var($name, $fallback);
}

/// This makes it so that the dark elevation colors are not applied when using
/// the light theme.
@mixin use-light-theme-elevation-colors {
  @each $z-value, $background-color in $dark-elevation-colors {
    @include set-dark-elevation-color($z-value, $light-theme-surface-color);
  }
}

/// This applies all the dark elevation colors for the dark theme.
@mixin use-dark-theme-elevation-colors {
  @each $z-value, $background-color in $dark-elevation-colors {
    @include set-dark-elevation-color($z-value, $background-color);
  }
}

/// Creates all the css variables for the light theme respecting feature flags
@mixin use-light-theme-colors {
  @include theme-set-var(inverse-color, $light-theme-inverse-color);
  @include theme-set-var(background-color, $light-theme-background-color);
  @if $disable-dark-elevation and not $disable-surface-color {
    @include theme-set-var(surface-color, $light-theme-surface-color);
  }
  @if not $disable-text-primary-color {
    @include theme-set-var(text-primary-color, $light-theme-text-primary-color);
  }
  @if not $disable-text-secondary-color {
    @include theme-set-var(
      text-secondary-color,
      $light-theme-text-secondary-color
    );
  }
  @if not $disable-text-hint-color {
    @include theme-set-var(text-hint-color, $light-theme-text-hint-color);
  }
  @if not $disable-text-disabled-color {
    @include theme-set-var(
      text-disabled-color,
      $light-theme-text-disabled-color
    );
  }

  @if not $disable-dark-elevation and $color-scheme != light {
    @include use-light-theme-elevation-colors;
  }
}

/// Creates all the css variables for the dark theme respecting feature flags
@mixin use-dark-theme-colors {
  @include theme-set-var(inverse-color, $dark-theme-inverse-color);
  @include theme-set-var(background-color, $dark-theme-background-color);
  @if $disable-dark-elevation and not $disable-surface-color {
    @include theme-set-var(surface-color, $dark-theme-surface-color);
  }
  @if not $disable-text-primary-color {
    @include theme-set-var(text-primary-color, $dark-theme-text-primary-color);
  }
  @if not $disable-text-secondary-color {
    @include theme-set-var(
      text-secondary-color,
      $dark-theme-text-secondary-color
    );
  }
  @if not $disable-text-hint-color {
    @include theme-set-var(text-hint-color, $dark-theme-text-hint-color);
  }
  @if not $disable-text-disabled-color {
    @include theme-set-var(
      text-disabled-color,
      $dark-theme-text-disabled-color
    );
  }

  @if not $disable-dark-elevation {
    @include use-dark-theme-elevation-colors;
  }
}

/// Creates all the default theme css variables respecting feature flags
@mixin theme-variables {
  @include theme-set-var(inverse-color, $inverse-color);
  @include theme-set-var(background-color, $background-color);
  @if $disable-dark-elevation and not $disable-surface-color {
    @include theme-set-var(surface-color, $surface-color);
  }

  @if not $disable-primary-color {
    @include theme-set-var(primary-color, $primary-color);
  }
  @if not $disable-on-primary-color {
    @include theme-set-var(on-primary-color, $on-primary-color);
  }
  @if not $disable-secondary-color {
    @include theme-set-var(secondary-color, $secondary-color);
  }
  @if not $disable-on-secondary-color {
    @include theme-set-var(on-secondary-color, $on-secondary-color);
  }
  @if not $disable-warning-color {
    @include theme-set-var(warning-color, $warning-color);
  }
  @if not $disable-on-warning-color {
    @include theme-set-var(on-warning-color, $on-warning-color);
  }
  @if not $disable-error-color {
    @include theme-set-var(error-color, $error-color);
  }
  @if not $disable-on-error-color {
    @include theme-set-var(on-error-color, $on-error-color);
  }
  @if not $disable-success-color {
    @include theme-set-var(success-color, $success-color);
  }
  @if not $disable-on-success-color {
    @include theme-set-var(on-success-color, $on-success-color);
  }
  @if not $disable-text-primary-color {
    @include theme-set-var(text-primary-color, $text-primary-color);
  }
  @if not $disable-text-secondary-color {
    @include theme-set-var(text-secondary-color, $text-secondary-color);
  }
  @if not $disable-text-hint-color {
    @include theme-set-var(text-hint-color, $text-hint-color);
  }
  @if not $disable-text-disabled-color {
    @include theme-set-var(text-disabled-color, $text-disabled-color);
  }

  @if not $disable-dark-elevation {
    @if $color-scheme == dark {
      @include use-dark-theme-elevation-colors;
    } @else {
      // default to light theme for system themes as well
      @include use-light-theme-elevation-colors;
    }
  }

  @include theme-set-var(outline-width, $outline-width);
  @include theme-set-var(outline-color, $outline-color);
  @if not $disable-outline-grey-color {
    @include theme-set-var(outline-grey-color, $outline-grey-color);
  }
}

/// Creates a new theme surface by conditionally updating theme background and
/// text colors for the elevation.
///
/// @param {Number} z-value - The material design elevation (0 - 24)
/// @param {Boolean} disable-colors [$disable-dark-elevation] - Set to `true`
/// if the `background-color` and `color` should not be updated.
@mixin create-surface($z-value, $disable-colors: $disable-dark-elevation) {
  @if not $disable-dark-elevation {
    @include theme-set-var(surface-color, get-dark-elevation-color($z-value));
  }
  @if not $disable-colors {
    @include theme-set-var(background-color, theme-get-var(surface-color));
    @include theme-use-var(background-color);
    @if not $disable-text-primary-color {
      @include theme-use-var(color, text-primary-color);
    }
  }
}

/// This is used to get another material design color with a different swatch
/// or accent.
///
/// @param {String} color - The material design color name
/// @param {Number} swatch - This should be: `100`, `200`, `300`, `400`, `500`,
/// `600`, `700`, `800`, or `900`. When `$accent` is set to `true`, it should
/// be: `100`, `200`, `400`, or `700`
/// @param {Boolean} accent [false] - Set to `true` to get an accent color
/// instead of a main color.
/// @param {Color|null} fallback-color [null] - A color to use if the `$color`
/// is not a material design color. Mostly for internal usage.
/// @param {String} fallback-name [null] - Used internally to provide a better
/// error message if a `$fallback-color` was not provided.
/// @returns {Color|null} The swatch or fallback color
@function get-swatch(
  $color,
  $swatch,
  $accent: false,
  $fallback-color: null,
  $fallback-name: null
) {
  $current-color-index: list.index(map.values(colors.$color-map), $color);

  @if not $current-color-index {
    @if not $fallback-color {
      $fallback: if(
        $fallback-name,
        "the '$#{$fallback-name}' variable",
        "a fallback color"
      );

      $error-msg: "Invalid material design color: '#{$color}'. If this was intentional because your app does " +
        "not use material design colors, set #{$fallback} instead to get a correct color for the provided swatch: " +
        "#{$swatch}.";
      @error $error-msg;
    } @else if meta.type-of($fallback-color) != "color" {
      @error "Invalid fallback color: '#{$fallback-color}'. This must be a valid color.";
    } @else {
      @return $fallback-color;
    }
  }

  $suffixes: utils.validate(
    if($accent, colors.$accent-suffixes, colors.$primary-suffixes),
    $swatch,
    "material design color swatch"
  );
  $current-color-name: list.nth(
    map.keys(colors.$color-map),
    $current-color-index
  );
  $accent-index: string.index($current-color-name, "-a-");
  @if $accent-index {
    $current-color-name: string.slice(
      $current-color-name,
      1,
      $accent-index - 1
    );
  } @else {
    $index: 1;
    $found: false;
    @while not $found and $index < list.length(colors.$available-color-names) {
      $suffix: list.nth(colors.$available-color-names, $index);
      $suffix-index: string.index($current-color-name, $suffix);
      @if $suffix-index {
        $found: true;
        $current-color-name: string.slice(
          $current-color-name,
          1,
          $suffix-index - 1 + string.length($suffix)
        );
      }

      $index: $index + 1;
    }
  }

  $color-name: "#{$current-color-name}#{if($accent, "-a", "")}-#{$swatch}";

  @return map.get(colors.$color-map, $color-name);
}

/// Generates the default `@media (prefers-color-scheme: dark)` styles if the
/// `$disable-default-system-theme` and `$disable-default-root-theme` are both
/// `false` and the `$color-scheme` has been set to `system`.
@mixin default-system-theme {
  @if (not $disable-default-system-theme) and
    (not $disable-default-root-theme) and
    ($color-scheme == system)
  {
    @media (prefers-color-scheme: dark) {
      :root {
        @content;
      }
    }
  }
}
